package at.ac.tuwien.ame

import com.google.common.collect.Table
import com.google.common.collect.HashBasedTable
import java.util.logging.Logger
import ftldsl.ModelElement
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.resource.Resource

import static extension at.ac.tuwien.ame.FTLUtils.*
import static extension com.google.common.collect.Iterables.*

/**
 * Maintains a table of scoped variables and their data types.
 */
class FTLCompiler {
	public static Resource resource
	private static Logger logger = Logger::getLogger(typeof(FTLCompiler).name)
	private static Table<String, EObject, String> varTable = HashBasedTable::create

	/**
	 * Deletes all variables defined in the given scope.
	 */
	def static V_EVICT(EObject scope) {
		logger.fine('''removed variables of «scope»''')
		if (scope == null) varTable.clear
		else varTable.column(scope).clear
	}

	/**
	 * Sets or adds a new variable within the given scope.
	 */
	def static V_SET(String name, EObject scope, String str) {
		logger.fine('''added variable «name» as «str» in scope «scope»''')
		varTable.put(name, scope, str)
	}

	/**
	 * Sets or adds a new variable within the given scope.
	 */
	def static V_SET(String name, EObject scope, ModelElement me) {
		V_SET(name, scope, me.name)
	}

	/**
	 * Retrieves the variable with the given name.
	 */
	def static V_GET(String name) {
		val vars = varTable.row(name)
		val v = if (vars.empty) null else vars.values.iterator.next
		logger.fine('''get variable «name» as «v»''')
		v
	}

	/**
	 * Attempts to determine the type of the given expression.
	 */
	def static inferType(String e) {
		val parts = e.split("\\.")
		val variable = V_GET(parts.get(0))

		if (variable == null)	return null

		var EClassifier clazz = classForName(resource, variable)
		
		if (clazz == null) variable
		else if (parts.size == 1) clazz.name
		else {
			if (variable.contains("Factory.eINSTANCE.create")) {
				var type = getUnqualifiedName(variable)
				type = type.substring(6, type.length - 2)
				clazz = (classForName(resource, type) as EClassifier)
				return if (clazz.instanceClass != null) clazz.instanceClass.name else clazz.name
			}

			val msg = '''Variable or method «parts.get(0)» undefined or not visible in scope: «e»'''.toString
			for (int i : 1..(parts.size - 1)) {
				val field = (clazz as EClass).getEStructuralFeature(parts.get(i))
				if (field != null) {
					if (field.many) {
						return '''org.eclipse.emf.common.util.EList<«field.EType.name»>'''.toString;
					}
					clazz = field.EType
				} else {
					val method = (clazz as EClass).EAllOperations.find(new EOperationPredicate(parts.get(i)), null)
					if (method == null)	throw new IllegalArgumentException(msg)
					clazz = method.EType
				}
			}
			if (clazz.instanceClass != null) clazz.instanceClass.name else clazz.name
		}
	}
}
